<?php

namespace app\common\controller\publics;

use think\Controller;
use think\Request;

class Jwt
{
    // 配置信息
    protected $config=[
        // api 默认版本号
        'version' => 'v1',
        // 可选 DataArray, Array
        'serializer' => 'DataArray',
        // 加密算法
        'algorithm' => 'HS256',
        // 默认hash_hmac加密私钥
        'key' => 'es-key-myblog',
        // RSA算法使用的私钥文件路径
        'privateKeyPath' => '/home/rsa_private_key.pem',
        // RSA算法使用的公钥文件路径
        'publicKeyPath' => '/home/rsa_public_key.pem',
        // 误差时间，单位秒
        'deviation' => 1,
        // 过期时间, 单位分钟
        'ttl' => 120,
    ];

    // 可用加密算法
    protected $algorithms = [
        'HS256' => ['hash', 'SHA256'],
        'HS512' => ['hash', 'SHA512'],
        'RS256' => ['openssl', 'SHA256'],
    ];

    // 默认加密算法
    protected $algorithm = 'HS256';

    // 默认hash_hmac加密私钥
    protected $key;

    // 默认openssl加密私钥路径
    protected $privateKeyPath;
    // 默认openssl加密公钥路径
    protected $publicKeyPath;

    // 默认openssl加密私钥
    protected $privateKey;
    // 默认openssl加密公钥
    protected $publicKey;

    protected $deviation = 0;


    function __construct()
    {
        error_reporting(5);//1+4
        $this->init();
    }

    protected function init()
    {
        //$config = $this->config;
        if (isset($this->config['algorithm']) && $this->config['algorithm']) {
            $this->algorithm = $this->config['algorithm'];
        }
        if (isset($this->config['key']) && $this->config['key']) {
            $this->key = $this->config['key'];
        }

        if (isset($this->config['deviation']) && $this->config['deviation']) {
            $this->deviation = $this->config['deviation'];
        }

        if (isset($this->config['privateKeyPath']) && $this->config['privateKeyPath'] && isset($this->config['publicKeyPath']) && $this->config['publicKeyPath']) {
            $this->privateKeyPath = 'file://' . $this->config['privateKeyPath'];
            $this->publicKeyPath = 'file://' . $this->config['publicKeyPath'];
        }

        if (empty($this->algorithms[$this->algorithm])) {
            exit('加密算法不支持');
            //throw new JWTException('加密算法不支持');
        }

        // 检查openssl支持和配置正确性
        if ('openssl' === $this->algorithms[$this->algorithm][0]) {
            if (!extension_loaded('openssl')) {
                exit('php需要openssl扩展支持');
                //throw new JWTException('php需要openssl扩展支持');
            }
            if (!file_exists($this->privateKeyPath) || !file_exists($this->publicKeyPath)) {
                exit('密钥或者公钥的文件路径不正确');
                //throw new JWTException('密钥或者公钥的文件路径不正确');
            }
            // 读取公钥和私钥
            $this->privateKey = openssl_pkey_get_private($this->privateKeyPath);
            $this->publicKey = openssl_pkey_get_public($this->publicKeyPath);
        }
    }

    public function encode64($data){
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }
    public function decode64($data){
        return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
    }
    /**
     * 加密
     */
    public function encode($payload, $key = null, $algorithm = null)
    {
        if ($key) {
            $this->key = $key;
        } else {
            if ('openssl' === $this->algorithms[$this->algorithm][0]) {
                $this->key = $this->privateKey;
            }
        }
        if ($algorithm) $this->algorithm = $algorithm;

        $header = ['typ' => 'JWT', 'alg' => $this->algorithm];
        $segments = [];
        // 编码第一部分 header
        $segments[] = self::encode64(json_encode($header));
        // 编码第二部分 payload
        $segments[] = self::encode64(json_encode($payload));

        // 第三部分为header和payload signature
        $signature_string = implode('.', $segments);
        $signature = $this->signature($signature_string);
        // 加密第三部分
        $segments[] = self::encode64($signature);

        return implode('.', $segments);
    }
    /**
     * 解码验证
     */
    public function decode($token, $key = null, $algorithm = null)
    {

        if ($key) {
            $this->key = $key;
        } else {
            if ('openssl' === $this->algorithms[$this->algorithm][0]) {
                $this->key = $this->publicKey;
            }
        }
        if ($algorithm) $this->algorithm = $algorithm;

        $segments = explode('.', $token);

        if (count($segments) != 3) {
            return false;
            //throw new JWTException('Token文本错误');
        }

        list($header64, $payload64, $signature64) = $segments;

        // 获取3个片段
        $header = json_decode(self::decode64($header64), false, 512, JSON_BIGINT_AS_STRING);
        $payload = json_decode(self::decode64($payload64), false, 512, JSON_BIGINT_AS_STRING);
        $signature = self::decode64($signature64);

        //获取jwt算法
        if (!isset($header->alg) || empty($header->alg)){
            return false;
        }

        // 验证签名
        if (!$this->verify("$header64.$payload64", $signature)) {
            return false;
            //throw new TokenInvalidException('无效的 Token');
        }

        //签发时间大于当前服务器时间验证失败
        if (isset($payload->iat) && $payload->iat > time()){
            return false;
        }

        // 在什么时间之前，该jwt都是不可用的
        if (isset($payload->nbf) && $payload->nbf > (time() + $this->deviation)) {
            return false;
            //throw new TokenNotBeforeException('该 Token 无法在当前时间使用');
        }

        // 检查是否过期
        if (isset($payload->exp) && (time() - $this->deviation) >= $payload->exp) {
            return false;
            //throw new TokenExpiredException('该 Token 已过期');
        }

        return $payload;
    }

    /**
     * jwt 第三部分签名
     * @param  [type] $data      [description]
     * @param  [type] $key       [description]
     * @param  [type] $algorithm [description]
     * @return [type]            [description]
     */
    public function signature($data)
    {
        list($func, $alg) = $this->algorithms[$this->algorithm];

        switch($func) {
            // hash_hmac 加密
            case 'hash':
                return hash_hmac($alg, $data, $this->key, true);
            // openssl 加密
            case 'openssl':
                $sign = '';
                $ssl = openssl_sign($data, $sign, $this->key, $alg);
                if (!$ssl) {
                    exit('OpenSSL无法签名数据');
                    //throw new JWTException("OpenSSL无法签名数据");
                }
                return $sign;
        }
    }

    public function verify($data, $signature)
    {
        list($func, $alg) = $this->algorithms[$this->algorithm];

        switch($func) {
            case 'hash':
                $hash = hash_hmac($alg, $data, $this->key, true);
                return hash_equals($signature, $hash);
            case 'openssl':
                $isVerify = openssl_verify($data, $signature, $this->key, $alg);
                if (!$isVerify) {
                    return false;
                }
                return $signature;
        }
        return false;
    }

    public function set_config($C=''){
        if(is_array($C)){
            foreach ($C as $key => $item) {
                $this->config[$key] = $item;
            }
        }
        return $this;
    }
    /**
     * 生成token
     * @$exp	    number		多少秒后过期
     * @$other	    array       payload部分参数设置
     * @$key        string      hash_hmac加密私钥
     * @algorithm   string  `   加密算法(HS256|HS512|RS256)
     * @return      stirng      返回token字符串
     */
    public function create_token($exp=30,$other=array(),$key=null,$algorithm = null){
        $payload = [
            'iss'    =>'jwt_admin',  //该JWT的签发者
            'iat'    =>time(),  //签发时间
            'exp'    =>time()+$exp,  //过期时间
            'nbf'    =>time(),  //该时间之前不接收处理该Token
            //'sub'    =>'www.admin.com',  //面向的用户
            //'jti'    =>md5(uniqid('JWT').time())  //该Token唯一标识
        ];
        //允许修改payload部分的键名
        $filter = ['id','uid','upass','name','iss','sub','jti','nbf','a_id','a_name','module','controller','action'];
        if(is_array($other)){
            foreach ($other as $k => $v) {
                if(in_array($k,$filter)){
                    $payload[$k] = $v;
                }
            }
        }
        ksort($payload);
        return $this->encode($payload,$key,$algorithm);
    }
    /**
     * 验证token
     * @$token      string  token字符串 ($header64.$payload64.$Signature )
     * @$fn         callable    回调函数,要求有返回值
     * @$key        string  hash_hmac加密私钥
     * @$algorithm  string  可用加密算法(HS256|HS512|RS256)
     * @return      boolean|object  验证成功返回payload部分数据
     */
    public function is_token($token = '',$fn='',$key = null, $algorithm = null){
        if(empty($token)){
            $g = $_GET;
            $p = $_POST;
            if (isset($g['token']) && !empty($g['token'])){
                $token = $g['token'];
            } elseif (isset($p['token']) && !empty($p['token'])){
                $token = $p['token'];
            } else{
                return false;
            }
        }
        if(!$payload = self::decode($token,$key,$algorithm)){
            return false;
        }
        if(isset($argument) && is_callable($fn)){
            $rv = $fn($payload);
            if(in_array($rv,[0,null,'',false])){
                return false;
            }
        }
        //$module = \think\facade\Request::module();  //获取当前的模块名
        //$controller = \think\facade\Request::controller();  //获取当前的控制器名
        //$action   = \think\facade\Request::action();  //获取当前的操作名(方法)
        //if(isset($payload->module) && !empty($payload->module) && $payload->module != $module){
        //    return false;
        //}
        //if(isset($payload->controller) && !empty($payload->controller) && $payload->controller != $controller){
        //    return false;
        //}
        //if(isset($payload->action) && !empty($payload->action) && $payload->action != $action){
        //    return false;
        //}

        return $payload;
    }


}
